/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */
package com.obixlabs.commons.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.obixlabs.commons.util.CharacterSource;
import com.obixlabs.commons.util.RandomCharacterGenerator;

/**
 * <p>
 * A base class for implementing {@link PasswordRequirement password rules}
 * which require that a password contain a certain type of character. For
 * example, it is used to implement
 * {@link MinNonAlpaNumericCharsPasswordRequirement the rule requiring
 * passwords to contain at least one non-alpha-numeric character}.
 * </p>
 */
public abstract class AbstractCharPresencePasswordRequirement extends
		AbstractPasswordRequirement
{
	private static final Logger logger = LoggerFactory
			.getLogger(AbstractCharPresencePasswordRequirement.class);

	/**
	 * <p>
	 * The default value for the field {@link #minimumRequiredChars}.
	 * </p>
	 */
	private static final int DEFAULT_MINIMUM_REQUIRED_CHARS = 1;

	/**
	 * <p>
	 * The minimum number of characters in a {@link PasswordBuffer password} 
	 * which must {@link #characterSatisfiesRequirement(char) satisfy the}
	 * conditions of the requirement. This defaults to a value of 
	 * {@value #DEFAULT_MINIMUM_REQUIRED_CHARS}.
	 * </p>
	 */
	private int minimumRequiredChars;

	/**
	 * <p>
	 * Alternative constructor to {@link #AbstractCharPresencePasswordRequirement(int)}, 
	 * which uses the default value for the {@link #minimumRequiredChars minimum
	 * number} of characters in a {@link PasswordBuffer password} required to 
	 * {@link #characterSatisfiesRequirement(char) meet the conditions} of the 
	 * requirement.
	 * </p>
	 */
	public AbstractCharPresencePasswordRequirement()
	{
		this(DEFAULT_MINIMUM_REQUIRED_CHARS);
	}

	/**
	 * <p>
	 * Delegates core initialisation to superclass constructor
	 * {@link AbstractPasswordRequirement#AbstractPasswordRequirement()}, but
	 * also allows the caller to specify the {@link #minimumRequiredChars
	 * minimum number} of characters in the {@link PasswordBuffer password}
	 * which must {@link #characterSatisfiesRequirement(char) meet the
	 * specifications} of the requirement.
	 * </p>
	 * 
	 * @param minimumRequiredChars
	 *            The {@link #minimumRequiredChars minimum number} of characters
	 *            in the {@link PasswordBuffer password} that must
	 *            {@link #characterSatisfiesRequirement(char) satisfy} the
	 *            requirement.
	 *            
	 * @throws IllegalArgumentException If the specified {@link #minimumRequiredChars
	 * minimum character count} is less than or equal to 0. 
	 */
	public AbstractCharPresencePasswordRequirement(int minimumRequiredChars)
	{
		super();
		setMinimumRequiredChars(minimumRequiredChars);
	}

	/**
	 * <p>
	 * Delegates core functionality to parent constructor
	 * {@link AbstractPasswordRequirement#AbstractPasswordRequirement(CharacterSource, int)
	 * }, but also allows the caller to specify the
	 * {@link #minimumRequiredChars minimum number} of characters in a {@link PasswordBuffer password},
	 * which must {@link #characterSatisfiesRequirement(char) meet the
	 * conditions} of the requirement for it to be satisfied.
	 * </p>
	 * 
	 * @param characterSource
	 *            See
	 *            {@link AbstractPasswordRequirement#AbstractPasswordRequirement(CharacterSource, int)
	 *            superclass constructor} for details.
	 * @param prime
	 *            See
	 *            {@link AbstractPasswordRequirement#AbstractPasswordRequirement(CharacterSource, int)
	 *            superclass constructor} for details.
	 * @param minimumRequiredChars
	 *            The {@link #minimumRequiredChars minimum number} of characters
	 *            in a given {@link PasswordBuffer password} which must
	 *            {@link #characterSatisfiesRequirement(char) satisfy} the
	 *            conditions imposed by this requirement.
	 *            
	 * @throws IllegalArgumentException If the specified {@link #minimumRequiredChars
	 * minimum character count} is less than or equal to 0. 
	 */
	public AbstractCharPresencePasswordRequirement(
			CharacterSource characterSource, int prime, int minimumRequiredChars)
	{
		super(characterSource, prime);
		setMinimumRequiredChars(minimumRequiredChars);
	}

	/**
	 * <p>
	 * Delegates core functionality to parent constructor
	 * {@link AbstractPasswordRequirement#AbstractPasswordRequirement(CharacterSource)
	 * }, but also allows the caller to specify the
	 * {@link #minimumRequiredChars minimum number} of characters in a {@link PasswordBuffer password},
	 * which must {@link #characterSatisfiesRequirement(char) meet the
	 * conditions} of the requirement for it to be satisfied.
	 * </p>
	 * 
	 * @param characterSource
	 *            See
	 *            {@link AbstractPasswordRequirement#AbstractPasswordRequirement(CharacterSource)
	 *            superclass constructor} for details.
	 * @param minimumRequiredChars
	 *            The {@link #minimumRequiredChars minimum number} of characters
	 *            in a given {@link PasswordBuffer password} which must
	 *            {@link #characterSatisfiesRequirement(char) satisfy} the
	 *            conditions imposed by this requirement.
	 *            
	 * @throws IllegalArgumentException If the specified {@link #minimumRequiredChars
	 * minimum character count} is less than or equal to 0. 
	 */
	public AbstractCharPresencePasswordRequirement(
			CharacterSource characterSource,
			int minimumRequiredChars)
	{
		super(characterSource);
		setMinimumRequiredChars(minimumRequiredChars);
	}

	@Override
	public boolean apply(PasswordBuffer buffer)
	{
		boolean result;

		char character;
		boolean requirementMet = false;
		int matches = 0;

		for (int i = 0; i < buffer.length() && !requirementMet; i++)
		{
			character = buffer.charAt(i);
			if (characterSatisfiesRequirement(character))
			{
				//make sure index cannot be overwritten later
				buffer.writeProtect(i); 
				matches++;
			}

			if (matches >= minimumRequiredChars) requirementMet = true;

			if (requirementMet) break;
		}

		if (!requirementMet)
		{
			if (logger.isDebugEnabled())
				logger.debug("Requirement not met. Will attempt to insert value into buffer.");

			char newChar;
			boolean successfullyModifiedBuffer = true;
			for (int i=0;i<(minimumRequiredChars-matches);i++)
			{
				newChar= generateCharacterToSatisfyRequirement();
				if (!insertIntoRandomIndexOrAppend(buffer, newChar, false))
				{
					successfullyModifiedBuffer = false;
					break;
				}
			}
			
			result = successfullyModifiedBuffer;
		}
		else result = true;

		return result;
	}

	/**
	 * <p>
	 * This method has the responsibility of generating a character satisfying
	 * this {@link PasswordRequirement password rule}. Such that an invocation
	 * of the method {@link #characterSatisfiesRequirement(char)} with the
	 * generated character would return a value of true.
	 * </p>
	 * 
	 * <p>
	 * The contract for this method delegates the means by which the character
	 * is generated to the {@link PasswordRequirement rule} implementation.
	 * However, the hierarchy of this class provides {@link #getRandom() access}
	 * to a {@link RandomCharacterGenerator random character generator} which is
	 * the recommended way to generate password characters.
	 * </p>
	 * 
	 * @return A character satisfying this {@link PasswordRequirement rule},
	 *         such that the method {@link #characterSatisfiesRequirement(char)}
	 *         will return <code>True</code> when invoked with such a character.
	 */
	public abstract char generateCharacterToSatisfyRequirement();

	/**
	 * <p>
	 * Tests if the specified character meets the specification of this
	 * {@link PasswordRequirement rule} implementation. For example, if this
	 * {@link PasswordRequirement rule} mandates that a password
	 * {@link MinNumericCharsPasswordRequirement must contain at least one
	 * numeric character}, then this method should only return true for the
	 * numbers <code>0-9</code>.
	 * </p>
	 * 
	 * @param character
	 *            The character to test.
	 * @return <code>True</code> if the character satisfies the
	 *         {@link PasswordRequirement password requirement} and
	 *         <code>False</code> otherwise.
	 */
	public abstract boolean characterSatisfiesRequirement(char character);

	/**
	 * <p>
	 * Internal mutator for field {@link #minimumRequiredChars}.
	 * </p>
	 * 
	 * @param minimumRequiredChars
	 *            The value to which the field is to be set.
	 * @throws IllegalArgumentException
	 *             If the specified value is less than or equal to 0.
	 */
	private void setMinimumRequiredChars(int minimumRequiredChars)
	{
		if (minimumRequiredChars <= 0)
			throw new IllegalArgumentException(
					"Minimum required chars must be greater than 0!");
		this.minimumRequiredChars = minimumRequiredChars;
	}
}// end class def